/*
 * PostgreSQL访问客户端SSL证书信息的模块
 *
 * 由Victor B. Wagner编写 <vitus@cryptocom.ru>, Cryptocom LTD
 * 本文件根据BSD风格许可证发布。
 *
 * contrib/sslinfo/sslinfo.c
 */

#include "postgres.h"

#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>

#include "access/htup_details.h"
#include "funcapi.h"
#include "libpq/libpq-be.h"
#include "miscadmin.h"
#include "utils/builtins.h"

/*
 * 在Windows上，<wincrypt.h>包含一个X509_NAME的#define，这打破了我们的
 * 使用OpenSSL版本该符号的能力，如果在<openssl/ssl.h>之后引入<wincrypt.h>...
 * 至少在某些构建中是这样的。我们无法通过重新排序#include来可靠地修复这一点，
 * 因为libpq/libpq-be.h包含了<openssl/ssl.h>。相反，这里再次取消该#define。
 */
#ifdef X509_NAME
#undef X509_NAME
#endif

PG_MODULE_MAGIC;

static Datum fc_X509_NAME_field_to_text(X509_NAME *fc_name, text *fc_fieldName);
static Datum fc_ASN1_STRING_to_text(ASN1_STRING *fc_str);

/*
 * 函数上下文，用于在重复调用中持久化数据。
 */
typedef struct
{
	TupleDesc	tupdesc;
} SSLExtensionInfoContext;

/*
 * 指示当前会话是否使用SSL
 *
 * 函数没有参数。返回布尔值。如果当前会话是SSL会话，则为真，
 * 如果是本地会话或非SSL会话，则为假。
 */
PG_FUNCTION_INFO_V1(ssl_is_used);
Datum ssl_is_used(PG_FUNCTION_ARGS)
{
	PG_RETURN_BOOL(MyProcPort->ssl_in_use);
}


/*
 * 返回当前使用的SSL版本。
 */
PG_FUNCTION_INFO_V1(ssl_version);
Datum ssl_version(PG_FUNCTION_ARGS)
{
	const char *fc_version;

	if (!MyProcPort->ssl_in_use)
		PG_RETURN_NULL();

	fc_version = be_tls_get_version(MyProcPort);
	if (fc_version == NULL)
		PG_RETURN_NULL();

	PG_RETURN_TEXT_P(cstring_to_text(fc_version));
}


/*
 * 返回当前使用的SSL密码套件。
 */
PG_FUNCTION_INFO_V1(ssl_cipher);
Datum ssl_cipher(PG_FUNCTION_ARGS)
{
	const char *fc_cipher;

	if (!MyProcPort->ssl_in_use)
		PG_RETURN_NULL();

	fc_cipher = be_tls_get_cipher(MyProcPort);
	if (fc_cipher == NULL)
		PG_RETURN_NULL();

	PG_RETURN_TEXT_P(cstring_to_text(fc_cipher));
}


/*
 * 指示当前客户端是否提供了证书
 *
 * 函数没有参数。返回布尔值。如果当前会话是SSL会话且客户端证书已验证，则为真，
 * 否则为假。
 */
PG_FUNCTION_INFO_V1(ssl_client_cert_present);
Datum ssl_client_cert_present(PG_FUNCTION_ARGS)
{
	PG_RETURN_BOOL(MyProcPort->peer_cert_valid);
}


/*
 * 返回用于建立当前会话的证书的序列号
 *
 * 函数没有参数。返回证书序列号，类型为数字，如果当前会话不使用SSL或
 * SSL连接是在未发送客户端证书的情况下建立的，则返回null。
 */
PG_FUNCTION_INFO_V1(ssl_client_serial);
Datum ssl_client_serial(PG_FUNCTION_ARGS)
{
	char fc_decimal[NAMEDATALEN];
	Datum		fc_result;

	if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
		PG_RETURN_NULL();

	be_tls_get_peer_serial(MyProcPort, fc_decimal, NAMEDATALEN);

	if (!*fc_decimal)
		PG_RETURN_NULL();

	fc_result = DirectFunctionCall3(numeric_in,
								 CStringGetDatum(fc_decimal),
								 ObjectIdGetDatum(0),
								 Int32GetDatum(-1));
	return fc_result;
}


/*
 * 将OpenSSL的ASN1_STRING结构转换为文本
 *
 * 将ASN1_STRING转换为文本，尽可能将所有字符转换为当前数据库编码。
 * 任何无效字符将被问号替换。
 *
 * 参数：str - OpenSSL ASN1_STRING结构。该结构的内存管理由调用者负责。
 *
 * 返回Datum，可以直接从C语言SQL函数返回。
 */
static Datum fc_ASN1_STRING_to_text(ASN1_STRING *fc_str)
{
	BIO		   *fc_membuf;
	size_t		fc_size;
	char		fc_nullterm;
	char	   *fc_sp;
	char	   *fc_dp;
	text	   *fc_result;

	fc_membuf = BIO_new(BIO_s_mem());
	if (fc_membuf == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("could not create OpenSSL BIO structure")));
	(void) BIO_set_close(fc_membuf, BIO_CLOSE);
	ASN1_STRING_print_ex(fc_membuf, fc_str,
						 ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
						  | ASN1_STRFLGS_UTF8_CONVERT));
	/* 确保 BIO 内容的空终止 */
	fc_nullterm = '\0';
	BIO_write(fc_membuf, &fc_nullterm, 1);
	fc_size = BIO_get_mem_data(fc_membuf, &fc_sp);
	fc_dp = pg_any_to_server(fc_sp, fc_size - 1, PG_UTF8);
	fc_result = cstring_to_text(fc_dp);
	if (fc_dp != fc_sp)
		pfree(fc_dp);
	if (BIO_free(fc_membuf) != 1)
		elog(ERROR, "could not free OpenSSL BIO structure");

	PG_RETURN_TEXT_P(fc_result);
}


/*
 * 返回指定X509_NAME结构的指定字段
 *
 * ssl_client_dn和ssl_issuer_dn函数的共同部分。
 *
 * 参数：X509_NAME *name - 证书的主题或颁发者
 * 参数：text fieldName - 字段名称字符串，如'CN'或commonName，
 *			  在OpenSSL ASN1 OID数据库中查找
 *
 * 返回对名称的适当部分应用ASN1_STRING_to_text的结果
 */
static Datum fc_X509_NAME_field_to_text(X509_NAME *fc_name, text *fc_fieldName)
{
	char	   *fc_string_fieldname;
	int			fc_nid,
				fc_index;
	ASN1_STRING *fc_data;

	fc_string_fieldname = text_to_cstring(fc_fieldName);
	fc_nid = OBJ_txt2nid(fc_string_fieldname);
	if (fc_nid == NID_undef)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("invalid X.509 field name: \"%s\"",
						fc_string_fieldname)));
	pfree(fc_string_fieldname);
	fc_index = X509_NAME_get_index_by_NID(fc_name, fc_nid, -1);
	if (fc_index < 0)
		return (Datum) 0;
	fc_data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(fc_name, fc_index));
	return fc_ASN1_STRING_to_text(fc_data);
}


/*
 * 返回客户端证书的指定字段的主题名称
 *
 * 接收字段名称（如 'commonName' 和 'emailAddress'）并
 * 返回转换为数据库编码的证书主题的相应部分。
 *
 * 参数：fieldname text - 将在 OpenSSL 对象
 * 标识符数据库中查找
 *
 * 返回具有相应值的文本字符串。
 *
 * 如果参数无法被 OpenSSL 转换为 ASN1 OID，则抛出错误。如果不存在客户端证书，或者证书中没有具有该名称的字段，则返回 null。
 */
PG_FUNCTION_INFO_V1(ssl_client_dn_field);
Datum ssl_client_dn_field(PG_FUNCTION_ARGS)
{
	text	   *fc_fieldname = PG_GETARG_TEXT_PP(0);
	Datum		fc_result;

	if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
		PG_RETURN_NULL();

	fc_result = fc_X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), fc_fieldname);

	if (!fc_result)
		PG_RETURN_NULL();
	else
		return fc_result;
}


/*
 * 返回客户端证书颁发者的指定字段名称
 *
 * 接收字段名称（如 'commonName' 和 'emailAddress'）并
 * 返回转换为数据库编码的证书主题的相应部分。
 *
 * 参数：fieldname text - 将在 OpenSSL 对象
 * 标识符数据库中查找
 *
 * 返回具有相应值的文本字符串。
 *
 * 如果参数无法被 OpenSSL 转换为 ASN1 OID，则抛出错误。如果不存在客户端证书，或者证书中没有具有该名称的字段，则返回 null。
 */
PG_FUNCTION_INFO_V1(ssl_issuer_field);
Datum ssl_issuer_field(PG_FUNCTION_ARGS)
{
	text	   *fc_fieldname = PG_GETARG_TEXT_PP(0);
	Datum		fc_result;

	if (!(MyProcPort->peer))
		PG_RETURN_NULL();

	fc_result = fc_X509_NAME_field_to_text(X509_get_issuer_name(MyProcPort->peer), fc_fieldname);

	if (!fc_result)
		PG_RETURN_NULL();
	else
		return fc_result;
}


/*
 * 返回当前客户端证书主题作为一个字符串
 *
 * 此函数返回当前 SSL 连接中使用的客户端
 * 证书的主题名称（subject），并将其转换为
 * 当前数据库编码。
 *
 * 返回文本数据。
 */
PG_FUNCTION_INFO_V1(ssl_client_dn);
Datum ssl_client_dn(PG_FUNCTION_ARGS)
{
	char		fc_subject[NAMEDATALEN];

	if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
		PG_RETURN_NULL();

	be_tls_get_peer_subject_name(MyProcPort, fc_subject, NAMEDATALEN);

	if (!*fc_subject)
		PG_RETURN_NULL();

	PG_RETURN_TEXT_P(cstring_to_text(fc_subject));
}


/*
 * 返回当前客户端证书颁发者作为一个字符串
 *
 * 此函数返回当前 SSL 连接中使用的客户端
 * 证书的颁发者的主题名称，转换为
 * 当前数据库编码。
 *
 * 返回文本数据。
 */
PG_FUNCTION_INFO_V1(ssl_issuer_dn);
Datum ssl_issuer_dn(PG_FUNCTION_ARGS)
{
	char		fc_issuer[NAMEDATALEN];

	if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
		PG_RETURN_NULL();

	be_tls_get_peer_issuer_name(MyProcPort, fc_issuer, NAMEDATALEN);

	if (!*fc_issuer)
		PG_RETURN_NULL();

	PG_RETURN_TEXT_P(cstring_to_text(fc_issuer));
}


/*
 * 返回关于可用 SSL 扩展的信息。
 *
 * 返回由以下值组成的记录集：
 * - 扩展的名称。
 * - 扩展的值。
 * - 扩展的关键状态。
 */
PG_FUNCTION_INFO_V1(ssl_extension_info);
Datum ssl_extension_info(PG_FUNCTION_ARGS)
{
	X509	   *fc_cert = MyProcPort->peer;
	FuncCallContext *fc_funcctx;
	int			fc_call_cntr;
	int			fc_max_calls;
	MemoryContext fc_oldcontext;
	SSLExtensionInfoContext *fc_fctx;

	if (SRF_IS_FIRSTCALL())
	{

		TupleDesc	fc_tupdesc;

		/* 为跨调用持久性创建一个函数上下文 */
		fc_funcctx = SRF_FIRSTCALL_INIT();

		/*
		 * 切换到适合多个函数调用的内存上下文
		 */
		fc_oldcontext = MemoryContextSwitchTo(fc_funcctx->multi_call_memory_ctx);

		/* 为跨调用持久性创建用户函数上下文 */
		fc_fctx = (SSLExtensionInfoContext *) palloc(sizeof(SSLExtensionInfoContext));

		/* 构造元组描述符 */
		if (get_call_result_type(fcinfo, NULL, &fc_tupdesc) != TYPEFUNC_COMPOSITE)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("function returning record called in context that cannot accept type record")));
		fc_fctx->tupdesc = BlessTupleDesc(fc_tupdesc);

		/* 将 max_calls 设置为证书中扩展的计数 */
		fc_max_calls = fc_cert != NULL ? X509_get_ext_count(fc_cert) : 0;

		if (fc_max_calls > 0)
		{
			/* 得到结果，跟踪它们 */
			fc_funcctx->max_calls = fc_max_calls;
			fc_funcctx->user_fctx = fc_fctx;
		}
		else
		{
			/* 当没有结果时的快速处理 */
			MemoryContextSwitchTo(fc_oldcontext);
			SRF_RETURN_DONE(fc_funcctx);
		}

		MemoryContextSwitchTo(fc_oldcontext);
	}

	/* 每次调用函数时完成的工作 */
	fc_funcctx = SRF_PERCALL_SETUP();

	/*
	 * 初始化每次调用的变量。
	 */
	fc_call_cntr = fc_funcctx->call_cntr;
	fc_max_calls = fc_funcctx->max_calls;
	fc_fctx = fc_funcctx->user_fctx;

	/* 当还有更多要发送时继续 */
	if (fc_call_cntr < fc_max_calls)
	{
		Datum		fc_values[3];
		bool		fc_nulls[3];
		char	   *fc_buf;
		HeapTuple	fc_tuple;
		Datum		fc_result;
		BIO		   *fc_membuf;
		X509_EXTENSION *fc_ext;
		ASN1_OBJECT *fc_obj;
		int			fc_nid;
		int			fc_len;

		/* 这需要一个 BIO */
		fc_membuf = BIO_new(BIO_s_mem());
		if (fc_membuf == NULL)
			ereport(ERROR,
					(errcode(ERRCODE_OUT_OF_MEMORY),
					 errmsg("could not create OpenSSL BIO structure")));

		/* 从证书中获取扩展 */
		fc_ext = X509_get_ext(fc_cert, fc_call_cntr);
		fc_obj = X509_EXTENSION_get_object(fc_ext);

		/* 获取扩展名称 */
		fc_nid = OBJ_obj2nid(fc_obj);
		if (fc_nid == NID_undef)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("unknown OpenSSL extension in certificate at position %d",
							fc_call_cntr)));
		fc_values[0] = CStringGetTextDatum(OBJ_nid2sn(fc_nid));
		fc_nulls[0] = false;

		/* 获取扩展值 */
		if (X509V3_EXT_print(fc_membuf, fc_ext, 0, 0) <= 0)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("could not print extension value in certificate at position %d",
							fc_call_cntr)));
		fc_len = BIO_get_mem_data(fc_membuf, &fc_buf);
		fc_values[1] = PointerGetDatum(cstring_to_text_with_len(fc_buf, fc_len));
		fc_nulls[1] = false;

		/* 获取关键状态 */
		fc_values[2] = BoolGetDatum(X509_EXTENSION_get_critical(fc_ext));
		fc_nulls[2] = false;

		/* 构建元组 */
		fc_tuple = heap_form_tuple(fc_fctx->tupdesc, fc_values, fc_nulls);
		fc_result = HeapTupleGetDatum(fc_tuple);

		if (BIO_free(fc_membuf) != 1)
			elog(ERROR, "could not free OpenSSL BIO structure");

		SRF_RETURN_NEXT(fc_funcctx, fc_result);
	}

	/* 完成 */
	SRF_RETURN_DONE(fc_funcctx);
}
